/////////////////////////////////////////////////////////////////////////
//
// Amuse Engine SDK - system/ccm
// Copyright( c) 2014.  All Rights Reserved
//
// File:		AESubject.cpp
// Author:		Gianluca Belardelli
// Date:		26/06/2014
//
/////////////////////////////////////////////////////////////////////////
#include <AEEngine.h>

AESubject::AESubject( void )
{
}

AESubject::~AESubject( void )
{
	PreDestruct();
}

AEUINT32 AESubject::Attach( AEObserverI *lpInObserver, AEUINT32 uiInIntrestBits, AEUINT32 uiID, AEUINT32 uiShiftBits )
{
	// If the following assertion fails, it means that Change Control Manager (CCM)
	// was modified to start using "shifts". Please update the code of this class
	// appropriately (original version did not have any meaningful support except
	// shifting inInterest on entry)
	AEASSERT_MSG ( !uiShiftBits, "AESubject::Attach: Interest bits are shifted. Read the comment to this assertion");

	// Since the intended usage model is to use this method from CCMs only, and 
	// their implementation provided by this framework ensures that pObs in nonzero
	// the following assertion should suffice.
	AEASSERT_MSG( lpInObserver, "AESubject::Attach: Valid pointer to observer object must be specified" );

#ifdef SUPPORT_CONCURRENT_ATTACH_DETACH_TO_SUBJECTS
	AEScopedLock lock( m_csObserverListMutex );
#endif

	// Framework's CCM implementation ensures that the following assertion always holds
	//AEASSERT_MSG( m_lstObserverList.Contains( lpInObserver ),
	//		"AESubject::Attach: Observer has already been attached. Use AESubject::UpdateInterestBits instead." );

	// Add the observer to our list of observers
	m_lstObserverList.AddElement( new AEObserverRequest( lpInObserver, uiInIntrestBits, uiID) );

	return 0;
}

AEUINT32 AESubject::Detach( AEObserverI *lpInObserver )
{
    AEUINT32 uiError = 1;

#ifdef SUPPORT_CONCURRENT_ATTACH_DETACH_TO_SUBJECTS
    AEScopedLock lock( m_csObserverListMutex );
#endif

    for( AEUINT32 i = 0; i<m_lstObserverList.GetSize(); i++ )
	{
		if( m_lstObserverList[i]->m_lpObserver == lpInObserver )
		{
			m_lstObserverList.Remove( m_lstObserverList[i] );
			uiError = 0;
			break;
		}
	}

    return uiError;
}

AEUINT32 AESubject::UpdateInterestBits( AEObserverI *lpInObserver, AEUINT32 uiInIntrestBitsMask )
{
    // No need to check for pObs being nonzero since the find below guarantees correct work in any case
    AEUINT32 uiError = 1;
	AEObserverRequest *lpObserverReq = 0L;

#ifdef SUPPORT_CONCURRENT_ATTACH_DETACH_TO_SUBJECTS
    AEScopedLock lock( m_csObserverListMutex );
#endif

    // Find the given observer in our observer list
    for( AEUINT32 i = 0; i<m_lstObserverList.GetSize(); i++ )
	{
		if( m_lstObserverList[i]->m_lpObserver == lpInObserver )
		{
			lpObserverReq = m_lstObserverList[i];
			break;
		}
	}

	if( lpObserverReq )
	{
#ifdef SUPPORT_CONCURRENT_ATTACH_DETACH_TO_SUBJECTS
        // We are under the lock in this case
        lpObserver->m_interestBits |= inInterest;
#else
        // No lock is used, but updates can happen concurrently. So use interlocked operation
        AEUINT32 uiPrevBits;
        AEUINT32 uiNewBits = long( lpObserverReq->m_uiInterestBits | uiInIntrestBitsMask );

		do
		{
			uiPrevBits = lpObserverReq->m_uiInterestBits;
		}while ( INTERLOCKED_CMPXCHG( lpObserverReq->m_uiInterestBits, uiNewBits, uiPrevBits ) != uiPrevBits );

		uiError = 0;
#endif
	}

	return uiError;
}

AEUINT32 AESubject::GetID ( AEObserverI *lpObserver ) const
{
    for( AEUINT32 i = 0; i<m_lstObserverList.GetSize(); i++ )
	{
		if( m_lstObserverList[i]->m_lpObserver == lpObserver )
			return m_lstObserverList[i]->m_uiMyID;
	}

	return AEInvalidSubjectID;
}

void AESubject::PostChanges( AEUINT32 uiInChangedMask )
{
#ifdef SUPPORT_CONCURRENT_ATTACH_DETACH_TO_SUBJECTS
	AEScopedLock lock( m_csObserverListMutex );
#endif

	AEObserverRequest *lpObserverReq;

    for( AEUINT32 i = 0; i<m_lstObserverList.GetSize(); i++ )
	{
		lpObserverReq = m_lstObserverList[i];

		AEUINT32 uiChangedBitsOfInterest = GetBitsToPost( *lpObserverReq, uiInChangedMask );
		if ( uiChangedBitsOfInterest )
			lpObserverReq->m_lpObserver->ChangeOccurred( this, uiChangedBitsOfInterest );
	}
}

AEUINT32 AESubject::GetPotentialSystemChangesMask( void )
{
	return 0;
}

void AESubject::PreDestruct( void )
{
    // THREAD SAFETY NOTE
    // Currently this method is called from the destructor only (that is it is 
    // never called concurrently). Thus it does not lock the list. If ever in 
    // the future it is called concurrently, then locking similar to that
    // in the CSubject::Detach method will have to be added.

    for( AEUINT32 i = 0; i<m_lstObserverList.GetSize(); i++ )
		m_lstObserverList[0]->m_lpObserver->ChangeOccurred( this, 0 );

    m_lstObserverList.RemoveAll();
}

AE_FORCEINLINE AEUINT32 GetBitsToPost( AESubject::AEObserverRequest &refRequest, AEUINT32 uiChangeMask )
{
	AEUINT32 uiChangedBitsOfInterest = refRequest.m_uiInterestBits & uiChangeMask;
	return uiChangedBitsOfInterest;
}
